import Client, { FileInfo } from 'ssh2-sftp-client';
import path from 'path';
import { ServerConfig, UploadOptions, DirectoryOperationResult, FileTransferResult } from './types';
import chalk from 'chalk';
import pLimit from 'p-limit';

export class SftpClient {
  private client = new Client();
  private config: ServerConfig;
  private limiter!: ReturnType<typeof pLimit>;

  constructor(config: ServerConfig) {
    this.config = config;
  }

  async connect(): Promise<void> {
    try {
      await this.client.connect(this.config);
      console.log(chalk.green(`Connected to server ${this.config.host} on port ${this.config.port}`));
    } catch (error: any) {
      throw new Error(`Failed to connect to server: ${error.message}`);
    }
    this.limiter = pLimit(4);
  }

  async uploadFolder(options: UploadOptions, files: string[]): Promise<FileTransferResult[]> {
    const conn = new Client();
    const results: Promise<FileTransferResult>[] = [];

    try {
      await this.ensureRemoteDirectory(options.remotePath);
      for (const file of files) {
        let tryCount = 0;
        results.push(
          this.limiter(() =>
            this.uploadFile(file, options).catch((err) => {
              if (tryCount >= 5) {
                console.log(chalk.red(err.message));
                return {
                  localPath: options.localPath,
                  remotePath:options.remotePath,
                  success: false,
                  message: err.message
                };
              }
              tryCount++
              console.log(chalk.red(`try ${tryCount} to upload ${file}`));
              return this.uploadFile(file, options)
            })
          )
        );
      }
      return await Promise.all(results);
    } catch (error: any) {
      console.log(`Failed to upload folder: ${error.message}`);
      throw error;
    } finally {
      conn.end();
    }
  }

  async uploadFile(localPath: string, options: UploadOptions): Promise<FileTransferResult> {
    const relativePath = path.relative(options.localPath, localPath);
    const remotePath = path.join(options.remotePath, relativePath).replace(/\\/g, '/');
    const remoteDir = path.dirname(remotePath).replace(/\\/g, '/');

    try {
      await this.ensureRemoteDirectory(remoteDir);
      const exists = await this.client.exists(remotePath);
      if (exists) {
        console.warn(chalk.yellow(`${remotePath} Directory already exists`))
        return { success: false, localPath, remotePath };
      }
      const stats = await this.client.put(localPath, remotePath);
      console.log(chalk.green(`${stats} successfully`));

      return {
        localPath,
        remotePath,
        success: true,
      };
    } catch (error: any) {
      throw new Error(`Failed to upload file: ${error.message}`);
    }
  }

  private async ensureRemoteDirectory(dir: string): Promise<DirectoryOperationResult> {
    try {
      const exists = await this.client.exists(dir);

      if (exists) {
        return { success: true, message: 'Directory already exists' };
      }

      await this.client.mkdir(dir, true); // 递归创建
      return { success: true, message: 'Directory created' };
    } catch (error: any) {
      return {
        success: false,
        message: `Directory creation failed: ${error.message}`
      };
    }
  }

}
